Title Banner

Previous Book Contents Book Index Next

Inside Macintosh: QuickDraw GX Programmer's Overview / Part 2 - The QuickDraw GX Programming Cookbook
Chapter 6 - Handling Graphics


Editing Shape Transforms

This programming recipe changes the behavior of the application created in the previous three recipes. With the code from this recipe, you allow users to scale a path shape.

When the user selects a path shape, the code from this recipe provides transform control handles rather than geometry control handles. When the
user presses the mouse button on a transform control handle, the code tracks the mouse movement, providing feedback as the user drags the mouse by scaling the path shape.

Figure 6-4 depicts this transform-editing mechanism.

Figure 6-4 Scaling a shape with transform control handles

Overview of Recipe Steps

The steps in this recipe show you how to:

    1. Create transform control handles
    2. Provide feedback by scaling the path
    3. Prepare to provide transform feedback
    4. Scale the path as the user drags the mouse
    5. Edit selected path transform

You need to follow each step in this recipe to provide transform controls and dragging feedback for path shapes.

Functions Used in This Recipe

QuickDraw GX functions used in this recipe:
GXNewPolygons"Geometric Shapes"
QuickDraw GX Graphics
GXSetShapeFill"Shape Objects"
QuickDraw GX Objects
GXSetShapeHitTest"Shape Objects"
QuickDraw GX Objects
GXGetShapeLocalBounds"View-Related Objects"
QuickDraw GX Objects
GXGetShapePoints"Geometric Shapes"
QuickDraw GX Graphics
GXSetShapeType"Shape Objects"
QuickDraw GX Objects
GXDisposeShape"Shape Objects"
QuickDraw GX Objects
GXCopyToShape"Shape Objects"
QuickDraw GX Objects
GXMoveShapeTo"Transform Objects"
QuickDraw GX Objects
GXRotateShape"Transform Objects"
QuickDraw GX Objects
GXGetShapeMapping"Transform Objects"
QuickDraw GX Objects
GXGetViewPortMouse"QuickDraw GX and the
Macintosh Environment"
QuickDraw GX Environment and Utilities
FixedDivide"QuickDraw GX Mathematics"
QuickDraw GX Environment and Utilities
GXSetShapeMapping"Transform Objects"
QuickDraw GX Objects
GXScaleShape"Transform Objects"
QuickDraw GX Objects"
GXSetShapeAttributes"Shape Objects"
QuickDraw GX Objects
GXScaleShape"Transform Objects"
QuickDraw GX Objects
GXSetShapeAttributes"Shape Objects"
QuickDraw GX Objects
GXIgnoreGraphicsNotice"Errors, Warnings, and Notices"
QuickDraw GX Environment and Utilities
GXPopGraphicsNotice"Errors, Warnings, and Notices"
QuickDraw GX Environment and Utilities
GXDrawShape "Shape Objects"
QuickDraw GX Objects

Standard Macintosh functions used in this recipe:
StillDown"Event Manager"
Macintosh Toolbox Essentials

This recipe gives a brief description of these functions; you can find complete reference information for these functions in the Inside Macintosh suite of books.

This function also uses a function from the QuickDraw GX libraries:
SetShapeFastXorTransfertransferMode library

Recipe Step Descriptions

In this section, each step is described individually.

  1. Create transform control handles

    The first thing you need to do is replace the definition of the MyCreatePathControlHandles function originally defined on page 197.
    The original version of this function created geometry control handles,
    while the new version creates transform control handles. In the new version, you need these local variables:

    int index;
    gxShape aScaleHandle;
    gxPoint cornerPoints[4];
    static long scaleHandle[] = {1, /* number of contours */
    4, /* number of points */
    ff(0), ff(0),
    fl(5.5), ff(0),
    ff(0), fl(5.5),
    ff(0), ff(0)};

    The cornerPoints array stores the positions of the four corners of the bounding rectangle of the selected shape.

    The scaleHandle array specifies the geometry of the small, triangular transform control handles.

    The definition of the scaleHandle array uses the fl macro. This macro is similar to the ff macro, except the fl macro converts a floating-point number (like 5.5) to a fixed-point number.

    You can use the scaleHandle array to create a polygon shape using
    this code:

    aScaleHandle = GXNewPolygons((gxPolygons *) scaleHandle);
    GXSetShapeFill(aScaleHandle, gxWindingFill);
    SetShapeFastXorTransfer(aScaleHandle, &gBlack, &gWhite);
    GXSetShapeHitTest(aHandle, gxBoundsPart, ff(1));

    The transform control handle polygon has a solid shape fill, an exclusive-OR transfer mode, and is hit-tested only on the bounds part.

    Now, you need to turn the single transform control handle into four--one for each corner of the path shape's bounding rectangle by calling

    MyFindCorners(pathToEdit, cornerPoints);

    The MyFindCorners function uses the GXGetShapeLocalBounds function
    to find the path's bounding rectangle and then it converts the bounding rectangle to a polygon, which changes the geometry from having two points--the upper-left and lower-right corners--to having four points--one for each corner. The function then uses the GXGetShapePoints function to copy those points into an array of points and then dispose of the bounding rectangle shape.

    void MyFindCorners(gxShape aShape, gxPoint *cornerPoints)
    {
    gxRectangle bounds;
    gxShape boundsShape;

        GXGetShapeLocalBounds(aShape, &bounds);
    boundsShape = GXNewRectangle(&bounds);

        GXSetShapeType(boundsShape, gxPolygonType);
    GXGetShapePoints(boundsShape, 1, 4, cornerPoints);

        GXDisposeShape(boundsShape);
    }

    Once you've found the four corner points, you can create the four transform control handles. First, make a copy of the transform control handle you created earlier and move the copy to the upper-left corner:

    gCurrent->controls[0] = GXCopyToShape(nil, aScaleHandle);

    GXMoveShapeTo(gCurrent->controls[0],
    cornerPoints[0].x - ff(5),
    cornerPoints[0].y - ff(5));

    To make the transform control handle for the upper-right corner, you rotate the original and then copy it and move it to the upper-right corner:

    GXRotateShape(aScaleHandle, ff(90), ff(0), ff(0));

    gCurrent->controls[1] = GXCopyToShape(nil, aScaleHandle);
    GXMoveShapeTo(gCurrent->controls[1],
    cornerPoints[1].x + ff(5),
    cornerPoints[1].y - ff(5));

    You create the lower-right and lower-left transform control handles similarly:

    GXRotateShape(aScaleHandle, ff(90), ff(0), ff(0));
    gCurrent->controls[2] = GXCopyToShape(nil, aScaleHandle);
    GXMoveShapeTo(gCurrent->controls[2],
    cornerPoints[2].x + ff(5),
    cornerPoints[2].y + ff(5));

    GXRotateShape(aScaleHandle, ff(90), ff(0), ff(0));
    gCurrent->controls[3] = GXCopyToShape(nil, aScaleHandle);
    GXMoveShapeTo(gCurrent->controls[0],
    cornerPoints[3].x - ff(5),
    cornerPoints[3].y + ff(5));

    Notice that each control handle is moved 5 points away from the real corners of the path shape--to give a little room between the handles and
    the path.

    After you've created the four copies of the transform control handle, you can dispose of the original one:

    GXDisposeShape(aScaleHandle);
  2. Provide feedback by scaling the path

    The other function you need to replace is the MyPathDrag function, originally defined on page 202. The original version of this function edits the path geometry while the user dragged the mouse. The new version edits the transform. Here is the flow of control for this function:

    void MyPathDrag(gxPoint *originalPoint, int whichControl)
    {
    /* Declare local variables -- see Step 3. */

        /* Prepare for loop -- see Step 3. */

        do {
    /* Provide feedback -- see Step 4. */
    } while (StillDown());

        /* Edit transform of selected path -- see Step 5. */
    }

    The next few steps--Steps 3 through 5--show how to implement the parts of the new version of the MyPathDrag function.

  3. Prepare to provide transform feedback

    The new MyPathDrag function scales the shape as the user drags the mouse. When you scale a shape with QuickDraw GX, you specify a scaling origin--the point that doesn't move as the shape is scaled. In this recipe, you define the scaling origin as the corner opposite from the corner on which the user pressed the mouse button. To calculate the origin, you'll have to find the four corner points of the path shape's bounding rectangle and then calculate which point is opposite the selected point. You can store the four corner points in this array:

    gxPoint cornerPoints[4];

    You store the origin point, once you've calculated it, in this point structure:

    gxPoint originPoint;

    Like the original version, the new version of the MyPathDrag function needs to keep track of the previous mouse position and the new mouse position as the user drags the mouse. You can use these point structures to store this information:

    gxPoint oldPoint,
    newPoint;

    As the user drags the mouse, the MyPathDrag function calculates how much to scale the path shape based on its original height and width (the height and width of the path shape when the user started pressed on the mouse button) and its new height and width (the height and width as indicated
    by the current mouse position). You can store these values in fixed-point variables:

    Fixed originalHeight, originalWidth,
    newHeight, newWidth;

    Fixed hScaleFactor = fixed1,
    vScaleFactor = fixed1;

    The MyPathDrag function also needs to store the original mapping property of the transform object of the selected path shape:

    gxMapping originalMapping;

    You also need to create two path shapes to continually redraw as the user drags the mouse:

    gxShape oldPath = nil, 
    newPath = nil;

    The MyPathDrag function begins by erasing the transform control handles:

    MyErasePathControlHandles();

    Then, the function finds the four corners of the bounding rectangle of
    the path shape and then calculates the scaling origin point as the corner opposite the one the user pressed on:

    MyFindCorners(gCurrent->selectedPath, cornerPoints);

    originPoint.x = cornerPoints[(whichControl+1)%4].x;
    originPoint.y = cornerPoints[(whichControl+1)%4].y;

    (Note that the whichControl variable is 1-based--that is, it has a value between 1 and 4. The cornerPoints array is 0-based--that is, its indexes
    are numbered from 0 to 3. Therefore, you add 1 to the whichControl value rather than 2, as you might expect.)

    The function then stores a copy of the path shape's mapping:

    GXGetShapeMapping(gCurrent->selectedPath, &originalMapping);

    The oldPoint variable, which represents the previous mouse position, is initialized to be the original point the user pressed on:

    oldPoint.x = originalPoint->x;
    oldPoint.y = originalPoint->y;

    The path shapes used to provide feedback are initialized to be copies of the selected path shape:

    oldPath = GXCopyToShape(nil, gCurrent->selectedPath);
    SetShapeFastXorTransfer(oldPath, &gBlack, &gWhite);
    newPath = GXCopyToShape(nil, oldPath);

    Notice that the transfer mode of the feedback path shapes is set to the exclusive-OR mode to provide for fast erasing and redrawing.

  4. Scale the path as the user drags the mouse

    The new version of the MyPathDrag function also uses a do loop to provide feedback while the user is dragging the mouse:

    do {
    GXGetViewPortMouse(gCurrent->viewPort, &newPoint);

        if (MyMouseMoved(newPoint, oldPoint)) {

            /* edit new path to reflect new mouse position */
    /* erase old path */
    /* draw new path */
    /* set up for next pass through do loop */
    }
    } while (StillDown());

    As in the previous version of the MyPathDrag function, the code in the do loop needs to edit the newPath path shape to reflect the new mouse position, erase the oldPath path shape, draw the newPath path shape, and set up for the next pass through the loop.

    To edit the new path shape to reflect the new mouse position, you first need to determine the scaling factors, using this code:

    newWidth = MyFixedAbs(newPoint.x - originPoint.x);
    newHeight = MyFixedAbs(newPoint.y - originPoint.y);

    newWidth = MyFixedMax(ff(1), newWidth);
    newHeight = MyFixedMax(ff(1), newHeight);

    hScaleFactor = FixedDivide(newWidth, originalWidth);
    vScaleFactor = FixedDivide(newHeight, originalHeight);

    QuickDraw GX provides the FixedDivide function; you need to provide
    the MyFixedAbs function (which finds the absolute value of a fixed-point number) and the MyFixedMax function (which returns the larger of two fixed-point numbers):

    #define MyFixedAbs(a) ((a) >= 0 ? (a) : -(a))

    #define MyFixedMax(a, b) ((a) >= (b) ? (a) : (b))

    You also need to adjust the scaling factors, depending on whether the user has dragged the mouse clear past the origin point. You can use this code:

    if ((newPoint.x < originPoint.x) ^ 
    (originalPoint->x < originPoint.x))
    hScaleFactor = -hScaleFactor;

    if ((newPoint.y < originPoint.y) ^ 
    (originalPoint->y < originPoint.y))
    vScaleFactor = -vScaleFactor;

    Once you have the scaling factors, you set the mapping of the new path back to the original mapping and then use the GXScaleShape function to scale the mapping to reflect the new mouse position:

    GXSetShapeMapping(newPath, &originalMapping);
    GXScaleShape(newPath,
    hScaleFactor, vScaleFactor,
    originPoint.x, originPoint.y);

    The GXScaleShape function can affect the mapping property of a shape's transform object or the actual geometric points of the shape's geometry directly, depending on whether you have set the map transform shape attribute of your path shapes. If you add the following line of code to your MyHandleCreatePath function, then all of your path shapes have this attribute set and the shape's transform object is affected, not the shape's geometry itself. (Even the feedback path shapes--oldPath and newPath-- have this attribute set, because they were originally created as copies of the selected shape.)

    GXSetShapeAttributes(gCurrent->selectedPath,
    gxMapTransformShape);

    As you debug your sample program, you may find that the GXSetShapeMapping function posts a mapping_already_set notice
    the first time you call it. You can suppress this notice using the GXIgnoreGraphicsNotice and GXPopGraphicsNotice functions, as in
    this code:

    GXIgnoreGraphicsNotice(mapping_already_set);
    GXSetShapeMapping(newPath, &originalMapping);
    GXPopGraphicsNotice();

    Once you've edited the transform of the new path, you can erase the old path and redraw the new path:

    GXDrawShape(oldPath);
    GXDrawShape(newPath);

    Finally, you can set up the variables for the next pass through the loop:

    GXCopyToShape(oldPath, newPath);
    oldPoint = newPoint;
  5. Edit the selected path transform

    When the user releases the mouse button, you can dispose of the two feedback path shapes:

    GXDisposeShape(oldPath);
    GXDisposeShape(newPath);

    Now you can edit the mapping property of the real path shape's transform object. You can use the hScaleFactor and vScaleFactor variables that were set in the do loop--they still have the most current values when you exit the loop:

    GXScaleShape(gCurrent->selectedPath,
    hScaleFactor, vScaleFactor,
    originPoint.x, originPoint.y);

    Finally, you need to redraw the transform control handles and update the contents of the window:

    MyDisposePathControlHandles();
    MyCreatePathControlHandles();

    MyDrawWindow();

Related Recipes

The previous three recipes, "Creating Geometric Shapes" beginning on page 179, "Selecting Shapes" beginning on page 193, and "Editing Shape Geometries" beginning on page 199, show how to allow the user to create, select, and edit path shape geometries. You should be familiar with the steps
of those recipes before using this recipe.

The next recipe, "Dragging Shapes Using Offscreen Bitmaps," shows how to provide a implement feedback in a different way. The code in that recipe allows a user to drag colored circles around a window. It uses offscreen buffering to provide smooth redrawing.

The recipes in Chapter 4, "Using the QuickDraw GX Environment," show you how to initialize QuickDraw GX and set up the QuickDraw GX debugging facilities. You should read the recipes in that chapter before using any recipes in this chapter.

The recipes in Chapter 5, "Using Macintosh Windows," show you how to create Macintosh windows, attach QuickDraw GX view ports to them, and implement zooming, resizing, and scrolling. You need to be familiar with the information in that chapter before you can display QuickDraw GX graphics in a Macintosh window.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
6 JUL 1996




Navigation graphic, see text links

Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help